The .NET type system is composed of classes, structures, enumerations, interfaces, and delegates. To begin exploration of these types, let's check out the role of the enumeration (or simply, enum) using a new Console Application project named FunWithEnums.
When building a system, it is often convenient to create a set of symbolic names that map to known numerical values. For example, if you are creating a payroll system, you may want to refer to the type of employees using constants such as vice president, manager, contractor, and grunt. C# supports the notion of custom enumerations for this very reason. For example, here is an enumeration named EmpType:
// A custom enumeration. enum EmpType { Manager, // = 0 Grunt, // = 1 Contractor, // = 2 VicePresident // = 3 }
The EmpType enumeration defines four named constants, corresponding to discrete numerical values. By default, the first element is set to the value zero (0), followed by an n + 1 progression. You are free to change the initial value as you see fit. For example, if it made sense to number the members of EmpType as 102 through 105, you could do so as follows:
// Begin with 102. enum EmpType { Manager = 102, Grunt, // = 103 Contractor, // = 104 VicePresident // = 105 }
Enumerations do not necessarily need to follow a sequential ordering, and need not have unique values. If (for some reason or another) it makes sense to establish your EmpType as shown here, the compiler continues to be happy:
// Elements of an enumeration need not be sequential! enum EmpType { Manager = 10, Grunt = 1, Contractor = 100, VicePresident = 9 }
By default, the storage type used to hold the values of an enumeration is a System.Int32 (the C# int); however, you are free to change this to your liking. C# enumerations can be defined in a similar manner for any of the core system types (byte, short, int, or long). For example, if you want to set the underlying storage value of EmpType to be a byte rather than an int, you can write the following:
// This time, EmpType maps to an underlying byte. enum EmpType : byte { Manager = 10, Grunt = 1, Contractor = 100, VicePresident = 9 }
Changing the underlying type of an enumeration can be helpful if you are building a .NET application that will be deployed to a low-memory device (such as a .NET-enabled cell phone or PDA) and need to conserve memory wherever possible. Of course, if you do establish your enumeration to use a byte as storage, each value must be within its range! For example, the following version of EmpType will result in a compiler error, as the value 999 cannot fit within the range of a byte:
// Compile time error! 999 is too big for a byte! enum EmpType : byte { Manager = 10, Grunt = 1, Contractor = 100, VicePresident = 999 }
Once you have established the range and storage type of your enumeration, you can use it in place of socalled "magic numbers." Because enumerations are nothing more than a user-defined data type, you are able to use them as function return values, method parameters, local variables, and so forth. Assume you have a method named AskForBonus(), taking an EmpType variable as the sole parameter. Based on the value of the incoming parameter, you will print out a fitting response to the pay bonus request:
class Program { static void Main(string[] args) { Console.WriteLine("**** Fun with Enums *****"); // Make a contractor type. EmpType emp = EmpType.Contractor; AskForBonus(emp); Console.ReadLine(); } // Enums as parameters. static void AskForBonus(EmpType e) { switch (e) { case EmpType.Manager: Console.WriteLine("How about stock options instead?"); break; case EmpType.Grunt: Console.WriteLine("You have got to be kidding..."); break; case EmpType.Contractor: Console.WriteLine("You already get enough cash..."); break; case EmpType.VicePresident: Console.WriteLine("VERY GOOD, Sir!"); break; } } }
Notice that when you are assigning a value to an enum variable, you must scope the enum name (EmpType) to the value (Grunt). Because enumerations are a fixed set of name/value pairs, it is illegal to set an enum variable to a value that is not defined directly by the enumerated type:
static void ThisMethodWillNotCompile() { // Error! SalesManager is not in the EmpType enum! EmpType emp = EmpType.SalesManager; // Error! Forgot to scope Grunt value to EmpType enum! emp = Grunt; }
The interesting thing about .NET enumerations is that they gain functionality from the System.Enum class type. This class defines a number of methods that allow you to interrogate and transform a given enumeration. One helpful method is the static Enum.GetUnderlyingType(), which as the name implies returns the data type used to store the values of the enumerated type (System.Byte in the case of the current EmpType declaration).
static void Main(string[] args) { Console.WriteLine("**** Fun with Enums *****"); // Make a contractor type. EmpType emp = EmpType.Contractor; AskForBonus(emp); // Print storage for the enum. Console.WriteLine("EmpType uses a {0} for storage", Enum.GetUnderlyingType(emp.GetType())); Console.ReadLine(); }
If you were to consult the Visual Studio 2010 object browser, you would be able to verify that the Enum.GetUnderlyingType() method requires you to pass in a System.Type as the first parameter. As fully examined in Chapter 16, Type represents the metadata description of a given .NET entity.
One possible way to obtain metadata (as shown previously) is to use the GetType() method, which is common to all types in the .NET base class libraries. Another approach is to make use of the C# typeof operator. One benefit of doing so is that you do not need to have a variable of the entity you wish to obtain a metadata description of:
// This time use typeof to extract a Type. Console.WriteLine("EmpType uses a {0} for storage", Enum.GetUnderlyingType(typeof(EmpType)));
Beyond the Enum.GetUnderlyingType() method, all C# enumerations support a method named ToString(), which returns the string name of the current enumeration's value. The following code is an example:
static void Main(string[] args) { Console.WriteLine("**** Fun with Enums *****"); EmpType emp = EmpType.Contractor; // Prints out "emp is a Contractor". Console.WriteLine("emp is a {0}.", emp.ToString()); Console.ReadLine(); }
If you are interested in discovering the value of a given enumeration variable, rather than its name, you can simply cast the enum variable against the underlying storage type. The following is an example:
static void Main(string[] args) { Console.WriteLine("**** Fun with Enums *****"); EmpType emp = EmpType.Contractor; // Prints out "Contractor = 100". Console.WriteLine("{0} = {1}", emp.ToString(), (byte)emp); Console.ReadLine(); }
Note The static Enum.Format() method provides a finer level of formatting options by specifying a desired format flag. Consult the .NET Framework 4.0 SDK documentation for full details of the System.Enum.Format() method.
System.Enum also defines another static method named GetValues(). This method returns an instance of System.Array. Each item in the array corresponds to a member of the specified enumeration. Consider the following method, which will print out each name/value pair within any enumeration you pass in as a parameter:
// This method will print out the details of any enum. static void EvaluateEnum(System.Enum e) { Console.WriteLine("=> Information about {0}", e.GetType().Name); Console.WriteLine("Underlying storage type: {0}", Enum.GetUnderlyingType(e.GetType())); // Get all name/value pairs for incoming parameter. Array enumData = Enum.GetValues(e.GetType()); Console.WriteLine("This enum has {0} members.", enumData.Length); // Now show the string name and associated value, using the D format // flag (see Chapter 3). for(int i = 0; i < enumData.Length; i++) { Console.WriteLine("Name: {0}, Value: {0:D}", enumData.GetValue(i)); } Console.WriteLine(); }
To test this new method, update your Main() method to create variables of several enumeration types declared in the System namespace (as well as an EmpType enumeration for good measure). The following code is an example:
static void Main(string[] args) { Console.WriteLine("**** Fun with Enums *****"); EmpType e2 = EmpType.Contractor; // These types are enums in the System namespace. DayOfWeek day = DayOfWeek.Monday; ConsoleColor cc = ConsoleColor.Gray; EvaluateEnum(e2); EvaluateEnum(day); EvaluateEnum(cc); Console.ReadLine(); }
The output is shown in Figure 4-3.
Figure 4-3 Dynamically discovering name/value pairs of enumeration types
As you will see over the course of this text, enumerations are used extensively throughout the .NET base class libraries. For example, ADO.NET makes use of numerous enumerations to represent the state of a database connection (e.g., opened or closed), the state of a row in a DataTable (e.g., changed, new, or detached). Therefore, when you make use of any enumeration, always remember that you are able to interact with the name/value pairs using the members of System.Enum.